import datetime
import os
import efinance as ef
import matplotlib
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import font_manager
import mplcyberpunk
import matplotlib.dates as mdates
import numpy as np
import scipy.stats as stats

from .pdFile import file_exists, file_to_pd, pd_to_file
from .sentinel import ArbitrageSentinel
from .config import (FONT_PATH, DATA_PATH, REPORT_PATH, 
                    ANALYSIS_CONFIG, FUTURES_CONFIG)

class FuturesArbitrageSystem:
    def __init__(self, data_path=DATA_PATH):
        self.data_path = data_path
        self.setup_matplotlib()
        
        # 加载分析配置
        self.config = ANALYSIS_CONFIG
        # 使用FUTURES_CONFIG的keys作为期货对列表
        self.catch_future_codes = [
            {
                'name': name,
                'code1': config['code1'],
                'code2': config['code2']
            }
            for name, config in FUTURES_CONFIG.items()
        ]
        
        # 初始化哨兵系统，使用配置中的窗口设置
        self.sentinel = ArbitrageSentinel(
            std_threshold=2.0,
            window_sizes=[
                self.config['windows']['short'],
                self.config['windows']['medium'],
                self.config['windows']['long']
            ]
        )

    def setup_matplotlib(self):
        """设置matplotlib的配置"""
        matplotlib.use('TkAgg')
        plt.style.use("cyberpunk")
        
        # 设置中文字体
        if not os.path.exists(FONT_PATH):
            raise FileNotFoundError(f"找不到字体文件: {FONT_PATH}\n"
                                  f"请确保字体文件存在于: {os.path.dirname(FONT_PATH)}")
            
        self.font_prop = font_manager.FontProperties(fname=FONT_PATH)
        plt.rcParams['font.family'] = self.font_prop.get_name()

    def calculate_price(self, symbol1, symbol2, pair_name):
        """计算两个期货合约的价差，并进行多维度分析"""
        df1 = ef.futures.get_quote_history(symbol1)
        df2 = ef.futures.get_quote_history(symbol2)
        df1.set_index('日期', inplace=True)
        df2.set_index('日期', inplace=True)
        
        # 获取最近2000个交易日的数据
        min_length = min(len(df1), len(df2), 2000)
        df1_last = df1.tail(min_length)
        df2_last = df2.tail(min_length)
        
        # 确保两个DataFrame的索引对齐
        df1_last, df2_last = df1_last.align(df2_last, join='inner')
        
        # 计算价差
        df_result = pd.DataFrame()
        df_result['收盘'] = df1_last['收盘'] - df2_last['收盘']
        df_result.index = df1_last.index
        
        # 获取品种特定配置
        custom_config = self.config['custom_params'].get(pair_name, {})
        
        # 计算带时间衰减的统计指标
        if custom_config.get('use_decay', True):
            weighted_mean, weighted_std = self.sentinel.calculate_weighted_stats(
                df_result, 
                decay_factor=self.config['decay_factor']
            )
            df_result['weighted_mean'] = weighted_mean
            df_result['weighted_std'] = weighted_std
        
        # 计算季节性特征
        if custom_config.get('seasonal_adjust', False):
            seasonal_stats = self.sentinel.analyze_seasonality(df_result)
            df_result.attrs['seasonal_stats'] = seasonal_stats
        
        # 计算趋势指标
        trend_analysis = self.sentinel.analyze_trend(df_result)
        df_result['trend'] = trend_analysis
        
        return df_result

    def calculate_all_price(self):
        """计算所有期货对的价差"""
        date_string = datetime.datetime.now().strftime("%Y-%m-%d")
        all_price_data = {}
        
        for future_pair in self.catch_future_codes:
            pair_name = future_pair['name']
            
            # 首先尝试从文件读取数据
            if file_exists(pair_name, date_string):
                print(f"从文件读取 {pair_name} 的历史数据")
                price_data = file_to_pd(pair_name, date_string)
            else:
                # 如果文件不存在，则获取新数据
                print(f"从网络获取 {pair_name} 的历史数据")
                try:
                    price_data = self.calculate_price(
                        future_pair['code1'],
                        future_pair['code2'],
                        pair_name
                    )
                    # 保存数据到文件
                    pd_to_file(price_data, pair_name, date_string)
                    print(f"已保存 {pair_name} 的历史数据")
                except Exception as e:
                    print(f"获取 {pair_name} 数据失败: {str(e)}")
                    continue
            
            all_price_data[pair_name] = price_data
        
        return all_price_data

    def plot_all_price(self):
        """绘制所有期货对的价差图表"""
        all_price_data = self.calculate_all_price()
        length = len(all_price_data)
        rows = (length + 1) // 2
        
        fig, axes = plt.subplots(rows, 2, figsize=(12, rows * 6))
        axes = axes.flatten()
        colors = plt.get_cmap('Dark2')
        
        # 获取有信号的期货对
        active_signals = []
        active_pairs = []
        
        # 遍历所有品种对，分析信号
        for pair_name, price_data in all_price_data.items():
            signal = self.sentinel.analyze_spread(price_data, pair_name)
            if signal["signal"] != "观望":
                active_signals.append(signal)
                active_pairs.append(pair_name)
        
        # 重新排序，将有信号的放在前面
        sorted_pairs = []
        for pair_name, price_data in all_price_data.items():
            is_active = pair_name in active_pairs
            sorted_pairs.append((pair_name, price_data, is_active))
        
        # 按是否有信号排序
        sorted_pairs.sort(key=lambda x: x[2], reverse=True)
        
        for i, (pair_name, price_data, is_active) in enumerate(sorted_pairs):
            if i >= len(axes):  # 确保不超出子图数量
                break
            
            plot_color = colors(i % 8)
            mean_value = price_data['收盘'].mean()
            
            # 绘制价差线
            axes[i].plot(price_data.index, price_data['收盘'], 
                        linewidth=0.5, label='价差', color=plot_color)
            
            # 设置标题
            title = f'{pair_name} (均值: {mean_value:.2f})'
            
            # 如果有信号，在标题中标注
            if is_active:
                signal = next(s for s in active_signals if s["pair_name"] == pair_name)
                title += f'\n[警告] {signal["signal"]}信号 (强度: {signal["strength"]})'
                axes[i].set_title(title, fontproperties=self.font_prop, color='red')
            else:
                axes[i].set_title(title, fontproperties=self.font_prop)
            
            # 添加移动平均线
            for window in [5, 20]:
                ma = price_data['收盘'].rolling(window=window).mean()
                axes[i].plot(price_data.index, ma, linestyle='--', label=f'MA{window}')
            
            # 设置图表样式
            for spine in axes[i].spines.values():
                spine.set_visible(True)
                spine.set_linewidth(2)
            
            # 设置x轴
            axes[i].set_xticks(price_data.index[::20])  # 每20个点显示一个刻度
            axes[i].set_xticklabels([str(date)[:7] for date in price_data.index[::20]],
                                   rotation=45, ha='right', fontproperties=self.font_prop)
            
            # 添加参考线
            min_value = price_data['收盘'].min()
            max_value = price_data['收盘'].max()
            mean_value = price_data['收盘'].mean()
            axes[i].axhline(mean_value, color='r', linestyle=':', label=f'均值: {mean_value:.2f}')
            axes[i].axhline(min_value, linestyle='--', linewidth=1, alpha=0.5, 
                           color='gray', label=f'最小值: {min_value:.2f}')
            axes[i].axhline(max_value, linestyle='--', linewidth=1, alpha=0.5, 
                           color='gray', label=f'最大值: {max_value:.2f}')
            
            # 添加图例
            axes[i].legend(loc='best', prop=self.font_prop)
            axes[i].grid(True, alpha=0.3)
        
        # 删除多余的子图
        for i in range(len(sorted_pairs), len(axes)):
            fig.delaxes(axes[i])
        
        # 调整布局
        plt.tight_layout()
        
        # 保存图表
        date_string = datetime.datetime.now().strftime("%Y-%m-%d")
        fig.savefig(f'{self.data_path}\\期货套利分析-{date_string}.png', dpi=200)
        plt.close(fig)

    def plot_normal_distribution(self, all_price_data):
        """绘制价差的正态分布图"""
        length = len(all_price_data)
        rows = (length + 1) // 2
        
        # 增加图片高度，确保每个子图有足够的显示空间
        fig, axes = plt.subplots(rows, 2, figsize=(15, rows * 7))
        axes = axes.flatten()
        colors = plt.get_cmap('Dark2')
        
        # 获取有信号的期货对
        active_signals = []
        active_pairs = []
        
        # 遍历所有品种对，分析信号
        for pair_name, price_data in all_price_data.items():
            signal = self.sentinel.analyze_spread(price_data, pair_name)
            if signal["signal"] != "观望":
                active_signals.append(signal)
                active_pairs.append(pair_name)
        
        # 重新排序，将有信号的放在前面
        sorted_pairs = []
        for pair_name, price_data in all_price_data.items():
            is_active = pair_name in active_pairs
            sorted_pairs.append((pair_name, price_data, is_active))
        
        sorted_pairs.sort(key=lambda x: x[2], reverse=True)
        
        for i, (pair_name, price_data, is_active) in enumerate(sorted_pairs):
            if i >= len(axes):
                break
            
            spread_data = price_data['收盘']
            
            # 修改为2%和98%的分位数
            lower_bound = np.percentile(spread_data, 2)
            upper_bound = np.percentile(spread_data, 98)
            
            # 剔除异常值
            trimmed_data = spread_data[(spread_data >= lower_bound) & 
                                     (spread_data <= upper_bound)]
            
            # 使用修剪后的数据计算统计量
            mean = trimmed_data.mean()
            std = trimmed_data.std()
            
            # 创建正态分布的x轴数据
            x = np.linspace(mean - 4*std, mean + 4*std, 100)
            # 计算正态分布的y值并转换为百分比
            y = stats.norm.pdf(x, mean, std) * 100
            
            # 绘制正态分布曲线
            axes[i].plot(x, y, 'b-', lw=2, label='理论正态分布')
            
            # 绘制直方图，将密度值乘以100转换为百分比
            hist_values, bins, _ = axes[i].hist(trimmed_data, bins=30, density=True, 
                                              alpha=0.6, color=colors(i % 8), 
                                              label='实际分布')
            # 重新设置直方图的高度
            for patch in axes[i].patches:
                patch.set_height(patch.get_height() * 100)
            
            # 添加垂直线标记
            axes[i].axvline(mean, color='r', linestyle='--', label=f'均值: {mean:.2f}')
            axes[i].axvline(mean + std, color='g', linestyle=':', label=f'+1σ: {(mean+std):.2f}')
            axes[i].axvline(mean - std, color='g', linestyle=':', label=f'-1σ: {(mean-std):.2f}')
            axes[i].axvline(mean + 2*std, color='y', linestyle=':', label=f'+2σ: {(mean+2*std):.2f}')
            axes[i].axvline(mean - 2*std, color='y', linestyle=':', label=f'-2σ: {(mean-2*std):.2f}')
            
            # 添加当前价差位置
            current_spread = spread_data.iloc[-1]
            axes[i].axvline(current_spread, color='purple', linestyle='-', 
                           label=f'当前价差: {current_spread:.2f}')
            
            # 使用修剪后的统计量计算z-score
            z_score = (current_spread - mean) / std if std != 0 else 0
            
            # 获取所有周期的Z得分
            signal = self.sentinel.analyze_spread(price_data, pair_name)
            z_scores_info = ""
            if 'signals' in signal:
                z_scores_info = "Z得分分析:\n"
                for period, data in signal['signals'].items():
                    period_name = str(period) + "日" if isinstance(period, int) else str(period)
                    z_scores_info += f"{period_name}: {data['z_score']:.2f} (权重:{data['weight']:.2f})\n"
                z_scores_info += f"加权Z得分: {signal['weighted_z_score']:.2f}\n"
            
            # 设置标题（添加Z得分信息）
            title = (f'{pair_name}\n'
                    f'{z_scores_info}\n'
                    f'均值: {mean:.2f}, 标准差: {std:.2f}\n'
                    f'当前价差: {current_spread:.2f}\n'
                    f'数据范围: [{lower_bound:.2f}, {upper_bound:.2f}]')
            
            # 如果有信号，在标题中标注
            if is_active:
                signal = next(s for s in active_signals if s["pair_name"] == pair_name)
                title += f'\n[警告] {signal["signal"]}信号 (强度: {signal["strength"]})'
                axes[i].set_title(title, fontproperties=self.font_prop, color='red')
            else:
                axes[i].set_title(title, fontproperties=self.font_prop)
            
            # 修改坐标轴标签
            axes[i].set_xlabel('价差值', fontproperties=self.font_prop)
            axes[i].set_ylabel('概率密度 (%)', fontproperties=self.font_prop)
            
            # 添加图例
            axes[i].legend(prop=self.font_prop, loc='best')
            axes[i].grid(True, alpha=0.3)
            
            # 调整Y轴范围，确保直方图显示清晰
            ymax = max(max(y), max(hist_values) * 100)
            axes[i].set_ylim(0, ymax * 1.1)  # 留出10%的空间
        
        # 删除多余的子图
        for i in range(len(sorted_pairs), len(axes)):
            fig.delaxes(axes[i])
        
        # 调整布局
        plt.tight_layout()
        
        # 保存图表
        date_string = datetime.datetime.now().strftime("%Y-%m-%d")
        fig.savefig(f'{self.data_path}\\期货套利分布-{date_string}.png', dpi=200)
        plt.close(fig)

    def run(self):
        """运行完整的分析流程"""
        print("开始运行期货套利分析系统...")
        all_price_data = self.calculate_all_price()
        
        print("生成价差趋势图...")
        self.plot_all_price()
        
        print("生成正态分布图...")
        self.plot_normal_distribution(all_price_data)
        
        print("分析完成！")


if __name__ == '__main__':
    system = FuturesArbitrageSystem()
    system.run()
